/*
*    Phantom Test Driver v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/*
 * SSI_ColorPane.java
 *
 * Created on October 5, 2005, 10:02 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package ssi_colorizer;

import javax.swing.JEditorPane;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import java.awt.Container;

import java.awt.Color;
import java.awt.Font;
import java.io.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.geom.Rectangle2D;

import ptd.PTDDisplay;

import java.awt.print.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.RepaintManager;
import java.awt.BasicStroke;
//import java.awt.im.InputContext;

import javax.swing.event.UndoableEditListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;
import javax.swing.undo.UndoManager;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CannotRedoException;

import javax.swing.Action;

import java.awt.dnd.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import ptd.Main;


/**
 *
 * @author John
 */

// Note that this is based on JTextComponent
public class SSI_ColorPane extends JEditorPane implements PTDDisplay, Printable, DropTargetListener{

    SSI_ColorKit MyColorKit;
    public JScrollPane MyScrollPane;
    public int EndOfOutput;
    
    public boolean bCanGetInput;
    public boolean bInputAvailable;
    public boolean bPassive;
    public boolean bTrackIndents;
    public String PrintName;
    public boolean bChanged;
    
    // -----------------------------------------------------
    public SSI_ColorPane() {
        
        //this.
        
        MyColorKit = new SSI_ColorKit();
        this.setEditorKit(MyColorKit);
        
        this.setEditorKitForContentType("text/phantom", MyColorKit);
                
	this.setContentType("text/phantom");
	this.setBackground(Color.white);
	this.setFont(new Font("Courier", 0, 12));
	this.setEditable(true);


        EndOfOutput = 0;
        bCanGetInput = false;
        bInputAvailable = false;
        bPassive = false;
        PrintName = new String();
        bChanged = false;
        bTrackIndents = false;
        PrintName = "";
        
        addKeyListener(new java.awt.event.KeyAdapter(){
            public void keyTyped(java.awt.event.KeyEvent evt) {
                m_KeyTyped(evt);
            }
            public void keyPressed(java.awt.event.KeyEvent evt) {
                m_KeyPressed(evt);
            }
        });
        

         addDocListeners();
        
        
       // this.addKeyListener(new SSI_ConsoleListener());
    }

    // -----------------------------------------------------  
    private void addDocListeners(){
        Document aDoc = getDocument();
        aDoc.addUndoableEditListener(new UEListener());

        aDoc.addDocumentListener(new DocumentListener(){
            public void changedUpdate(DocumentEvent e){
                bChanged = true;
            }
            public void insertUpdate(DocumentEvent e){
                bChanged = true;
            }
            public void removeUpdate(DocumentEvent e){
                bChanged = true;
            }
        });
    }
    
    // -----------------------------------------------------    
    private void m_KeyTyped(java.awt.event.KeyEvent evt) {                                        
        
        if(bPassive){
            if(bCanGetInput != true){
                evt.consume();
            }else{
                Document aDoc = this.getDocument();
                if(getCaretPosition() < EndOfOutput){
                    setCaretPosition(aDoc.getLength());
                }
            }
        }
        
        if(evt.getKeyChar() == '\n'){
        //if(evt.getKeyCode() == evt.VK_ENTER){
            if(bCanGetInput){
                bInputAvailable = true;
            }else{
                bInputAvailable = false;
            }           
        }
    }    

    // -----------------------------------------------------    
    private void m_KeyPressed(java.awt.event.KeyEvent evt) {                                        
        if(evt.getKeyCode() == KeyEvent.VK_ENTER){
            if(bTrackIndents){
                int iPos = getCaretPosition();
                String sLine = getCaretLine();
                String sIndent = "";
                for(int ctr = 0; ctr < sLine.length(); ctr++){
                    if(sLine.charAt(ctr) == ' '){
                        sIndent = sIndent + ' ';
                    }else if(sLine.charAt(ctr) == '\t'){
                        sIndent = sIndent + '\t';
                    }else{
                        break;
                    }
                }
                evt.consume();
                insertText("\n" + sIndent, iPos);
            }
        }
    }    
    
    // -----------------------------------------------------        
    public String getInput(){
        String Ret = "";
        Document aDoc = this.getDocument();
        try{
            Ret = aDoc.getText(EndOfOutput, aDoc.getLength() - EndOfOutput);
            EndOfOutput = aDoc.getLength();
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }
        
        // cut-off and remove at first newline
        int i = Ret.indexOf("\n", 0);
        if(i >= 0){
            Ret = Ret.substring(0, i);
        }
        bInputAvailable = false;
        return Ret;
    }
    
    // -----------------------------------------------------
    public String getCaretLine(){
        //OK
        int CarPos = getCaretPosition();
        String RetLine = new String();         
        SSI_ColorDocument aDoc = (SSI_ColorDocument)getDocument();
        try{
            String s = aDoc.getText(0, CarPos);
            int nlPos = s.lastIndexOf("\n");
            if(nlPos < 0){
                nlPos = 0;
            }else{
                nlPos = nlPos + 1;
            }
            RetLine = s.substring(nlPos, CarPos);
            Main.debug_out("Line is: " + RetLine);
        }catch(Exception ex){
            Main.debug_out(ex.getMessage());
        }        
        return RetLine;
    }
    
    // -----------------------------------------------------
    public void addText(String newText){
        
        SSI_ColorDocument aDoc = (SSI_ColorDocument)this.getDocument();
        try{
            if(bPassive){
                setCaretPosition(aDoc.getLength());
            }
            Position p = aDoc.getEndPosition();
            int i = p.getOffset();
            aDoc.insertString(this.getSelectionEnd(), newText, null);
            
            this.setSelectionStart(i+newText.length());
            this.setSelectionEnd(i+newText.length());
            //Main.debug_out("Writing HTML Line: " + newText);
            
            // Did you know Java is a piece of crap?
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }
        
        try{
            EndOfOutput = aDoc.getLength();
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }
        
        
        
/*        
        // OK
        int i = getText().length();
        setSelectionStart(i);
        setSelectionEnd(i);
        replaceSelection(newText);
        
        // Set Caret Location
        i = getText().length();
        setSelectionStart(i);
        setSelectionEnd(i);
        
        Document aDoc = this.getDocument();
        try{
            EndOfOutput = aDoc.getLength();
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }
 */
    }

    // -----------------------------------------------------
    public void insertText(String newText, int iPos){

        SSI_ColorDocument aDoc = (SSI_ColorDocument)this.getDocument();
        try{
            aDoc.insertString(iPos, newText, null);
            this.setSelectionStart(iPos+newText.length());
            this.setSelectionEnd(iPos+newText.length());
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }

        try{
            EndOfOutput = aDoc.getLength();
        }catch (Exception e){
            Main.debug_out(e.getMessage());
        }
    }
    
    
  public int getLineHeight()
  {
    FontMetrics fm = getFontMetrics(this.getFont());
    int height = fm.getHeight();
    //int textFontAscent = fm.getAscent();
    return height;
    
    //FontMetrics fm = getFontMetrics(textFont);
    //textFontHeight = fm.getHeight();
    //textFontAscent = fm.getAscent();
    //textTopInset = text.getInsets().top;

      
    //int lastPos = sizes.getPosition(index) + textTopInset;
    //int height = textFontHeight;
    
    /*
    try
    {
      Element map = getDocument().getDefaultRootElement();
      int n = map.getElementCount();
      if(index >= n){
          return height;
      }
      
      int lastChar = map.getElement(index).getEndOffset() - 1;
      Rectangle r = modelToView(lastChar);
      height = (r.y - lastPos) + r.height;
    }
    catch (BadLocationException ex)
    {
      ex.printStackTrace();
    }
    return height;
     **/
  }
    
  public int countPrintLines(){
      String s = getText();
      s.replaceAll("\r", "");
      String[] Lines = s.split("\n");
      return Lines.length;
  }
    
    // -----------------------------------------------------    
    public int lineFromPos(int Pos){
        
        // Taken from http://forum.java.sun.com/thread.jspa?threadID=613385
        try {
            int y = modelToView(getCaretPosition()).y;
            int line = y/getLineHeight();
            return ++line;
        } catch (BadLocationException e) {
        }
        return -1;


        /*
        int index = 0;
        int numLines = 1;
        String s;
        
        String s_all = this.getText();
        int tlen = s_all.length();
        
        try{
            if(Pos <= 0){
                return 1;
            }
            
            s = this.getText();
            s = s.substring(0, Pos);
            //this.getText(0, Pos-1);//getText();
            s.replaceAll("\r", "");
            while(true){
                index = index + 1;
                index = s.indexOf("\n", index);
                if(index < 0) break;
                numLines = numLines + 1;
            }

        }catch (Exception e){
            Main.debug_out("Error in line counter");
            Main.debug_out(e.getMessage());
        }
        
        
        return numLines;
                 * */
    }
    
    // -----------------------------------------------------
    public class SSI_ConsoleListener implements KeyListener{
         public void keyPressed(KeyEvent e) {
             if(e.getKeyCode() == e.VK_ENTER){
                 Main.debug_out("ENTER was typed");
                 SSI_ColorPane a = (SSI_ColorPane)e.getComponent();
                 a.getCaretLine();
             }
             
         }
         public void keyReleased(KeyEvent e) {
             
         }
         
         public void keyTyped(KeyEvent e) {
                 
         }
    }
    
    // -----------------------------------------------------
    public int loadFile(String filename){
        try{
            
            // this is how we read
            File aFile = new File(filename);
            FileInputStream anInput = new FileInputStream(aFile);
            this.read(anInput, null);
            addDocListeners();
        }catch (Exception e){
            ptd.PTDMessageBox aMsg = new ptd.PTDMessageBox("Could not open file: " + filename);
            aMsg.setVisible(true);
            Main.debug_out(e.getMessage());
            return 0;
        }
        return 1;
    }
    
    // -----------------------------------------------------
    public int saveFile(String filename){
        try{
            // This is how we write
            File aFile = new File(filename);
            FileOutputStream anOutput = new FileOutputStream(aFile);
            OutputStreamWriter aWriter = new OutputStreamWriter(anOutput);
            this.write(aWriter);
            bChanged = false;
            
        }catch (Exception e){
            Main.debug_out(e.getMessage());
            return 0;
        }
                
        return 1;
    }
    
    public int display(String Output){
        //if(Output.length() <= 0) return 1;  // So buffer blank buffer doesnt accidentally destroy segment position
        addText(Output);
        //SegmentPosition = getText().length();
        return 1;
    }

    public int displayFormatted(String Output){
        return display(Output);
    }
    
    public int displayError(String Output){
        return display(Output);
    }
    
    public int displayWarning(String Output){
        return display(Output);
    }
    
    public int displayFailure(String Output){
        return display(Output);
    }

    public int displayDebug(String Output){
        // Is this ever used?
        return display(Output);
    }
    
    public int startSuite(String Name){
        return 0;
    }
    
    public int startTestcase(String Name){
        return 0;
    }

    public int startScript(String Name){
        return 0;
    }    
    
    public int stopSuite(){
        return 0;
    }
    
    public int stopTestcase(){
        return 0;
    }

    public int stopScript(){
        return 0;
    }    
    
    public int startProcessor(int Type, String Name){
        return 0;
    }

    public int stopProcessor(){
        return 0;
    }

    
    public int SearchNext(int index, String Phrase, boolean bCaseSensitive){
        if(Phrase.length() <= 0){
            return -1;
        }
        
        String sText = getText();
        
        sText = sText.replaceAll("\r", "");
        //sText = sText.replaceAll("\n", "");
        
        if(bCaseSensitive == false){
            sText = sText.toLowerCase();
            Phrase = Phrase.toLowerCase();
        }
        
        if(index < 0){
            index = 0;
        }
        if(index > sText.length()){
            index = 0;
        }
        
        index = sText.indexOf(Phrase, index);
        
        //int nlPos = lineFromPos(index);
        //nlPos = nlPos - 1;
        int nlPos = 0;
        
        SSI_ColorDocument aDoc = (SSI_ColorDocument)this.getDocument();

        if(index >= 0){
            this.setSelectionStart(index - nlPos);
            this.setSelectionEnd(index + Phrase.length() - nlPos);
        }
        return index;
    }

    public int ReplaceSel(int index, String Phrase){
        if((getSelectionEnd() - getSelectionStart()) <= 0){
            return 0;
        }
        replaceSelection(Phrase);
        return 1;
         
    }

    public int print (Graphics g, PageFormat pf, int pageIndex) throws PrinterException{
        
        //Based on code from http://www.codecomments.com/message197072.html
        Graphics2D g2 = (Graphics2D)g;                          // Re-cast the graphics object
        g2.setColor (Color.black);                              // Print in all black

        RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);   // Un-double buffer
        Dimension d = this.getSize();
        double panelWidth = d.width;
        double panelHeight = d.height;
        double pageWidth = pf.getImageableWidth();
        double pageHeight = pf.getImageableHeight();
        
        double scale = 0.75;      // Descale the text so more fits on a page
        //double scale = 1.0;      // Descale the text so more fits on a page
        double line_margin = 54;  // 3/4 an inch for line numbers
        
        int iH = getLineHeight();            // Calculate line height
        int nH = (int)(iH*scale);            // Descale line height
        scale = ((double)nH)/((double)iH);   // Adjust scale for integer values of pixel sizes
        iH = nH;                             // Save scaled height
        int NumLines = (int)(pageHeight/iH); // Get number of scaled lines

        Main.debug_out("NumLines: " + Integer.toString(NumLines));
        
        int totalNumPages = (int)Math.ceil(scale * panelHeight / pageHeight);

        // Check for empty pages
        if (pageIndex >= totalNumPages) return Printable.NO_SUCH_PAGE;
        
        // g2 represents the page canvas, *not* the control
        // so translate to the upper left corner of the page
        //g2.translate(pf.getImageableX() + line_margin, pf.getImageableY());
        g2.translate(pf.getImageableX(), pf.getImageableY());
        
        // Translate to the beginning of the page of interest
        // g2.translate(0f, -pageIndex * pageHeight);
        g2.translate(0f, (-pageIndex*iH*NumLines));
        
        // Set the font to the font of the page
        g2.setFont(this.getFont());
        Font F = this.getFont();

        // Re-set the clip so only prints an integer number of lines
        Rectangle r = new Rectangle();
        r = g2.getClipBounds(r);
        r.y = r.y + this.getFontMetrics(F).getDescent();
        r.x = r.x - (int)line_margin;
        r.width = r.width + (int)line_margin;
        r.height = NumLines*iH;  // Oh just a fudge, dangit...
        
        // Set the clip and draw title
        g2.setClip(r.x, r.y-54, r.width, r.height);
        double titleX = (pf.getImageableWidth () / 2) - (this.getFontMetrics(F).stringWidth (PrintName) / 2);
        //titleX = titleX - line_margin;
        g2.drawString (PrintName, (int)titleX, -28 + pageIndex*iH*NumLines);
        g2.setClip(r.x, r.y, r.width, r.height);
        
        // Set the clip and draw page number
        g2.setClip(r.x, r.y, r.width, r.height + 54);
        titleX = (pf.getImageableWidth () / 2) - (this.getFontMetrics(F).stringWidth (String.valueOf(pageIndex+1)) / 2);
        //titleX = titleX - line_margin;
        g2.drawString (String.valueOf(pageIndex+1), (int)titleX, 28 + (pageIndex+1)*iH*NumLines);
        g2.setClip(r.x, r.y, r.width, r.height);

        // Hide the caret and paint the page
        Caret c = this.getCaret();
        c.setVisible(false);
        g2.scale(scale, scale);
        this.paint(g2);

        // Paint line numbers
        int ctr = 0;
        int TotalLines = countPrintLines();  //lineFromPos(this.getText().length());
        int lmH = this.getFontMetrics(F).getHeight();
        int iX = (int)(-1*line_margin);
        int iY = pageIndex*lmH*NumLines; //(int)(pf.getImageableY() + pageIndex*iH*NumLines);
        for(ctr = 0; ctr < NumLines; ctr++){
            iY = iY + lmH;
            if(ctr+pageIndex*NumLines+1 <= TotalLines){
                g2.drawString (String.valueOf(ctr+pageIndex*NumLines+1) + ":", iX, iY);
            }
        }
        
        g2.scale(1/scale, 1/scale);
        c.setVisible(true);
        
        RepaintManager.currentManager(this).setDoubleBufferingEnabled(true);   // Un-double buffer

        // Old border drawing code
        //g2.setStroke (new BasicStroke (1));
        //Rectangle2D.Double border = new Rectangle2D.Double(0, pf.getImageableHeight()*(pageIndex),  pf.getImageableWidth(),  pf.getImageableHeight());
        //Rectangle2D.Double border = new Rectangle2D.Double(0, pageIndex*iH*NumLines,  pf.getImageableWidth(),  iH*NumLines);
        //g2.draw (border);

        return Printable.PAGE_EXISTS;
    }
    
    // ----------- INTERNAL CLASSES -----------
    protected class UEListener implements UndoableEditListener {
        public void undoableEditHappened(UndoableEditEvent e) {
            
            //Remember the edit and update the menus.
            undoMObj.addEdit(e.getEdit());
            ua.updateUndoState();
            ra.updateRedoState();
        }
   }

   public class UAction extends AbstractAction {
        public UAction() {
            super("Undo");
            setEnabled(false);
        }
                 
        public void actionPerformed(ActionEvent e) {
            try {
                if(undoMObj.canUndo()){
                    undoMObj.undo();
                }
            } catch (CannotUndoException ex) {
                Main.debug_out("Unable to undo: " + ex);
                ex.printStackTrace();
            }
            updateUndoState();
            ra.updateRedoState();
        }
          
        protected void updateUndoState() {
            if (undoMObj.canUndo()) {
                setEnabled(true);
                putValue(Action.NAME, undoMObj.getUndoPresentationName());
            } else {
                setEnabled(false);
                putValue(Action.NAME, "Undo");
            }
        }      
   }
   
   public class RAction extends AbstractAction {
        public RAction() {
            super("Redo");
            setEnabled(false);
        }
                 
        public void actionPerformed(ActionEvent e) {
            
            try {
                if(undoMObj.canRedo()){
                    undoMObj.redo();
                }
            } catch (CannotRedoException ex) {
                Main.debug_out("Unable to redo: " + ex);
                ex.printStackTrace();
            }
            updateRedoState();
            ua.updateUndoState();
        }
          
        protected void updateRedoState() {
            if (undoMObj.canRedo()) {
                setEnabled(true);
                putValue(Action.NAME, undoMObj.getRedoPresentationName());
            } else {
                setEnabled(false);
                putValue(Action.NAME, "Redo");
            }
        }      
   }
   
   // Add drag gesture recognized call...
   
   //The Drag operation has terminated with a Drop on this DropTarget
   public void drop(DropTargetDropEvent dtde) {
        Transferable transferable = dtde.getTransferable();

        //flavor not supported, reject drop
        if (!transferable.isDataFlavorSupported( DataFlavor.getTextPlainUnicodeFlavor() )) {
            dtde.rejectDrop();
            return;
        }
        
        try{
            String data = (String)transferable.getTransferData( DataFlavor.getTextPlainUnicodeFlavor() );
            addText(data);
        }catch (Exception err){
            Main.debug_out(err.getMessage());
        }
   }
   
   public void dragExit(DropTargetEvent dtde) {
       
   }
   
   public void dragEnter(DropTargetDragEvent dtde) {
       
   }
   
   public void dragOver(DropTargetDragEvent dtde) {
       
   }
   
   public void dropActionChanged(DropTargetDragEvent dtde) {
       
   }
   
   /*
   public void dragGestureRecognized(DragGestureEvent e) {
        Main.debug_out("Drag Started...");
        DefaultMutableTreeNode dragNode = getSelectedTreeNode();
        if (dragNode != null) {
            Transferable transferable = (Transferable) dragNode.getUserObject();

            Cursor cursor = DragSource.DefaultCopyNoDrop;
            int action = e.getDragAction();
            if (action == DnDConstants.ACTION_MOVE){
                cursor = DragSource.DefaultMoveNoDrop;
            }
        }
    }
    **/
   
   // Edit undo/redo stuff
   protected UndoManager undoMObj = new UndoManager();
   public UAction ua= new UAction();
   public RAction ra= new RAction();

}
